#include "cn_zyyl_ai_face_FaceDetector2.h"
#include <seeta/QualityAssessor.h>

// cn.zyyl.ai.face.SeetaRect
// cn.zyyl.ai.face.modle

static seeta::FaceDetector *FD;
static seeta::FaceLandmarker *PD;
static seeta::FaceDatabase *FDB;
static seeta::QualityAssessor *QA;

static float threshold = 0.7f;
//0:false 1:true
static jboolean printLog = 0;

// 最小人脸识别的大小，默认为80，越大速度越快，越小速度越慢
static std::int8_t minFaceSize = 80;
// 是否启用自动人脸注册，默认生成一个ID号进行标示，默认0关闭 【0：关闭，1打开】
//static std::int8_t isItAutoRegister = 0;
//是否提供5points功能 默认0关闭【0：关闭，1打开】
static std::int8_t isItProvidePoints = 0;


long getCurrentTime()
{
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

jstring tranString(JNIEnv *env, jstring str) {
  const char *c_str = NULL;
  char buff[128] = {0};
  c_str = env->GetStringUTFChars(
      str, NULL); /* 获得传入的字符串，将其转换为native Strings */
  if (c_str == NULL) /* c_str == NULL意味着JVM为native String分配内存失败 */
  {
    return NULL;
  }
  sprintf(buff, "%s", c_str);
  env->ReleaseStringUTFChars(str, c_str); /* 通知JVM释放String所占的内存 */
  return env->NewStringUTF(
      buff); /* 构造新的Java.lang.String，如果JVM分配内存失败，则抛出OutOfMemoryError，并且返回NULL
              */
}

JNIEXPORT jstring JNICALL
Java_cn_zyyl_ai_face_FaceDetector2_version(JNIEnv *env, jobject) {
  return env->NewStringUTF("v0.0.3.6");
}

JNIEXPORT jint JNICALL Java_cn_zyyl_ai_face_FaceDetector2_facesCount(JNIEnv *,
                                                                     jobject) {
  return FDB->Count();
}
JNIEXPORT jboolean JNICALL Java_cn_zyyl_ai_face_FaceDetector2_deleteFace(
    JNIEnv *env, jobject,jlong id,jstring _faceFilesPath){
    int result = 0;
  if(id >=0){
    const char *faceFilesPath = env->GetStringUTFChars(_faceFilesPath, 0);
    FDB->Delete(id);
    FDB->Save(faceFilesPath);
    env->ReleaseStringUTFChars(_faceFilesPath, faceFilesPath);
    result= 1;
  }
  env->DeleteLocalRef(_faceFilesPath);
  return result;
}
JNIEXPORT jlongArray JNICALL Java_cn_zyyl_ai_face_FaceDetector2_DBIds(JNIEnv *env,
                                                                   jobject) {
    if(FDB != NULL){
        int size = FDB->Count();
        if(size > 0){
            long *ids = new long[size];
            FDB->DBIds(ids);
            if(printLog) {
                LOGI("Native line 77 Info DBIds: ids point addr %d %p", size, ids);
            }
            jlongArray result;
            result = env->NewLongArray(size);
            if (result == NULL) {
                if(printLog) {
                    LOGI("Native line 83 Error DBIds:NewLongArray(size) failed");
                }
                return NULL; /* out of memory error thrown */
            }
            jlong fill[size];

            for (int i = 0; i < size; i++) {
                // put whatever logic you want to populate the values here.
                fill[i] = ids[i];
                if(printLog) {
                    LOGI("Native line 93 DBIds:fill ids in loop step: %d value: %ld",
                         i,fill[i]);
                }
            }
            // move from the temp structure to the java structure
            env->SetLongArrayRegion(result, 0, size, fill);
            delete []ids;
            ids = NULL;
            return result;
        }else{
            if(printLog) {
                LOGI("Native line 104 Error DBIds:face db size 0");
            }
         return NULL;
        }
    }else{
        if(printLog) {
            LOGI("Native line 110 Error DBIds:FDB Engine is NULl");
        }
        return NULL;
    }
}

JNIEXPORT jint JNICALL Java_cn_zyyl_ai_face_FaceDetector2_construct(
    JNIEnv *env, jobject, jstring fdModel, jstring pdModel, jstring frModel,
    jint _minFaceSize, jint _isItProvidePoints,
    jstring _faceFilesPath,jboolean _printLog) {
  const char *fdModelFile = env->GetStringUTFChars(fdModel, 0);
  const char *pdModelFile = env->GetStringUTFChars(pdModel, 0);
  const char *frModelFile = env->GetStringUTFChars(frModel, 0);
  const char *faceFilesPath = env->GetStringUTFChars(_faceFilesPath, 0);

  printLog = _printLog;

  minFaceSize = _minFaceSize;
  isItProvidePoints = _isItProvidePoints;

  if(printLog){
      LOGI("Native line 131 Info construct:printLog: %d",printLog);
      LOGI("Native line 132 Info construct: loading models fdModel pdModel frModel "
           "faceFilesPath and CurrentTime: \n%s \n%s \n%s \n%s \n%ld \n",
           fdModelFile, pdModelFile, frModelFile, faceFilesPath,getCurrentTime());
  }


  seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO;
  int id = 0;
  seeta::ModelSetting FD_model(fdModelFile, device, id);
  seeta::ModelSetting PD_model(pdModelFile, device, id);
  seeta::ModelSetting FR_model(frModelFile, device, id);

  FD = new seeta::FaceDetector(FD_model);
  FD->set(seeta::FaceDetector::PROPERTY_VIDEO_STABLE, 1);
  FD->set(seeta::FaceDetector::PROPERTY_THRESHOLD1, 0.65f);
  FD->set(seeta::FaceDetector::PROPERTY_MIN_FACE_SIZE, minFaceSize);

  PD = new seeta::FaceLandmarker(PD_model);
  FDB = new seeta::FaceDatabase(FR_model);
  QA = new seeta::QualityAssessor();

    if(printLog) {
        LOGI("Native line 154 Info construct: loading faces faceFilesPath start");
    }
  if (!file_exists(faceFilesPath)) {
    FDB->Save(faceFilesPath);
  } else {
    FDB->Load(faceFilesPath);
  }
    if(printLog) {
        LOGI("Native line 162 Info construct: loading faces faceFilesPath end");
        LOGI("Native line 163 Info construct: config: minFaceSize:%d",
             minFaceSize);
        LOGI("Native line 165 Info construct: config: isItProvidePoints:%d",
             isItProvidePoints);
        LOGI("Native line 167 Info construct:config: m_db size: %ld",
             FDB->Count());
        LOGI("Native line 169 Info construct: loading models finished");
    }
  env->ReleaseStringUTFChars(fdModel, fdModelFile);
  env->ReleaseStringUTFChars(pdModel, pdModelFile);
  env->ReleaseStringUTFChars(frModel, frModelFile);
  env->ReleaseStringUTFChars(_faceFilesPath, faceFilesPath);
  return 1;
}
/*
 * Class:     cn_zyyl_ai_face_FaceDetector2
 * Method:    construct
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT jint JNICALL Java_cn_zyyl_ai_face_FaceDetector2_constructv1(
    JNIEnv *env, jobject, jstring filename) {
//  const char *detectModelFile = env->GetStringUTFChars(filename, 0);
//  seeta::ModelSetting::Device device = seeta::ModelSetting::AUTO;
//  int id = 0;
//    if(printLog) {
//        LOGI("constructv1: fileName : %s",
//             detectModelFile);
//    }
//  seeta::ModelSetting FD_model(detectModelFile, device, id);
//  FD = new seeta::FaceDetector(FD_model);
//  FD->set(seeta::FaceDetector::PROPERTY_VIDEO_STABLE, 1);
//  FD->set(seeta::FaceDetector::PROPERTY_THRESHOLD1, 0.65f);
//  env->ReleaseStringUTFChars(filename, detectModelFile);
  return 0;
}

/*
 * Class:     cn_zyyl_ai_face_FaceDetector2
 * Method:    dispose
 * Signature: ()V
 */
JNIEXPORT jint JNICALL Java_cn_zyyl_ai_face_FaceDetector2_dispose(JNIEnv *,
                                                                  jobject) {
  if (FD != NULL) {
    delete FD;
    FD = NULL;
  }

  if (PD != NULL) {
    delete PD;
    PD = NULL;
  }

    if (FDB != NULL) {
        delete FDB;
        FDB = NULL;
    }

    if (QA != NULL) {
        delete QA;
        QA = NULL;
    }

  return 1;
}

bool file_exists(const std::string &name) {
  struct stat buffer;
  return (stat(name.c_str(), &buffer) == 0);
}


JNIEXPORT jfloat JNICALL Java_cn_zyyl_ai_face_FaceDetector2_QA(
        JNIEnv *env, jobject, jint width, jint height,
        jint channels, jint length, jbyteArray data){
        float result = 0;
    auto timeStart = getCurrentTime();
    if (QA != NULL && PD != NULL && FD != NULL) {
        jbyte *cData = env->GetByteArrayElements(data, 0);
        unsigned char *bdata = (unsigned char *)cData;
        seeta::ImageData imageData(bdata, width, height, channels);
        if(printLog) {
            LOGI("Native line 245 Info QA:"
                 "width:%d height:%d channels:%d length:%d imageData.width:%d" ,
                 width,height,channels,length,imageData.width);
        }
        auto faces = FD->detect(imageData);
        if(faces.size > 0){
            if(printLog) {
                LOGI("Native line 252 Info QA : faces.size : %d", faces.size);
            }
            std::vector<SeetaPointF> points(PD->number());
            PD->mark(imageData, faces.data[0].pos, points.data());

            auto score = QA->evaluate(imageData, faces.data[0].pos, points.data());
            if(printLog) {
                LOGI("Native line 259 Info QA : score : %f", score);
            }
            result = score;
        }
    }else{
        if(printLog) {LOGI("Native line 264 Error QA : QA PD or FD is NUll");}
    }
    if(printLog) {
        LOGI("Native line 267 Info QA : times : %ld", getCurrentTime() - timeStart);
    }
    return result;
}


JNIEXPORT jint JNICALL Java_cn_zyyl_ai_face_FaceDetector2_Register(
    JNIEnv *env, jobject, jint width, jint height, jint channels, jint length,
    jbyteArray data, jstring _faceFilesPath) {
  const char *faceFilesPath = env->GetStringUTFChars(_faceFilesPath, 0);

  int64_t uId = -1;
  int64_t index = -1;
  float similarity = 0;
  auto queried = 0;
  if (FD != nullptr) {
    jbyte *cData = env->GetByteArrayElements(data, 0);
    unsigned char *bdata = (unsigned char *)cData;
    seeta::ImageData imageData(bdata, width, height, channels);
      if(printLog) {
          LOGI("Native line 287 Info Register FD Addr: %p"
               "width:%d height:%d channels:%d length:%d imageData.width:%d" ,
               FD,width,height,channels,length,imageData.width);
      }

    auto faces = FD->detect(imageData);
      if(faces.size > 0){
          if(printLog) {
              LOGI("Native line 295 Info Register : faces.size : %d", faces.size);
          }
          std::vector<SeetaPointF> points(PD->number());
          PD->mark(imageData, faces.data[0].pos, points.data());

          queried = FDB->QueryTop(imageData, points.data(), 1, &index, &similarity);
          // no face queried from database
          // 没有从库中找到
          if (queried < 1) {

          }
          // similarity greater than threshold, means recognized
          // 相似度大于阀值，表示识别
          if (similarity > threshold) {
              uId = index;
              if(printLog) {
                  LOGI("Native line 311 Info Register face existed!");
              }
          }else{
              if(printLog) {
                  LOGI("Native line 315 Info Register recognize no face,so execute register !!!");
              }
              uId = FDB->Register(imageData, points.data());
              FDB->Save(faceFilesPath);
              if(printLog) {
                  LOGI("Native line 320 Info Register uId: %ld", uId);
                  LOGI("Native line 321 Info Register db count: %ld", FDB->Count());
              }
          }

          if(printLog) {
              LOGI("Native line 326 Info Register face recognize result %d %ld %f", queried, index, similarity);
              LOGI("Native line 327 Info Register face recognize 相似度、阀值 %f %f", similarity,threshold);
              LOGI("Native line 328 Info Register faceFilesPath: %s",
                   faceFilesPath);
              LOGI("Native line 330 Info Register file_exists: %d",(int) file_exists(faceFilesPath));
              LOGI("Native line 331 Info Register uId: %ld", uId);
              LOGI("Native line 332 Info Register db count: %ld",FDB->Count());
          }
      }else{
          if(printLog) {
              LOGI("Native line 336 Info Register detector face size: %d",
                   faces.size);
          }
      }
  } else {
      if(printLog) {
          LOGI("Native line 342 Error Register: FD is NULL");
      }
  }
  env->DeleteLocalRef(data);
  env->ReleaseStringUTFChars(_faceFilesPath, faceFilesPath);
  return uId;
}

JNIEXPORT jint JNICALL Java_cn_zyyl_ai_face_FaceDetector2_Recognizer(
        JNIEnv *env, jobject, jint width, jint height, jint channels, jint length,
        jbyteArray data){
    //-2:FD is null
    int result = -2;
    if (FD != nullptr) {
        jbyte *cData = env->GetByteArrayElements(data, 0);
        unsigned char *bdata = (unsigned char *)cData;
        seeta::ImageData imageData(bdata, width, height, channels);
        auto timeStart = getCurrentTime();
        auto faces = FD->detect(imageData);
        if(printLog) {
            LOGI("Native line 362 Info Recognizer face detect times : %ld", getCurrentTime() - timeStart);
            LOGI("Native line 363 Info Recognizer faces : faces.size : %d", faces.size);
        }
        if (faces.size > 0) {
            auto queried = 0;
            int64_t index = -1;
            float similarity = 0;
            timeStart = getCurrentTime();
            std::vector<SeetaPointF> points(PD->number());
            PD->mark(imageData, faces.data[0].pos, points.data());
            if(printLog) {
                LOGI("Native line 373 Info Recognizer face PD->mark times : %ld", getCurrentTime() - timeStart);
            }
            timeStart = getCurrentTime();
            queried = FDB->QueryTop(imageData, points.data(), 1, &index, &similarity);//queried = FDB->QueryAbove(imageData, points.data(),threshold, 1, &index, &similarity);
            if(printLog) {
                LOGI("Native line 378 Info Recognizer face FDB->QueryTop times : %ld", getCurrentTime() - timeStart);
                LOGI("Native line 379 Info Recognizer function recognize similarity queried: %d", queried);
                LOGI("Native line 380 Info Recognizer function recognize index: %ld", index);
            }
            if(index < 0){index = -3;}

            // 相似度大于阀值，表示识别
            if (similarity > threshold) {result = index;}else{result = -3;}
        }else{
            //-3:no face
            if(printLog) {
                LOGI("Native line 389 Info Recognizer no face return -3");
            }
            result = -3;
        }

    }
    env->DeleteLocalRef(data);
    return result;
}

JNIEXPORT jint JNICALL Java_cn_zyyl_ai_face_FaceDetector2_Detect(
    JNIEnv *env, jobject, jint width, jint height, jint channels, jint length,
    jbyteArray data, jobject face1, jobject face2, jobject seetaPointsFace) {
  if (FD != nullptr) {
      if(printLog) {
          LOGI("Native line 404 Info Detector FD point addr: %p"
               "width:%d height:%d channels:%d length:%d",
               FD,width,height,channels,length);
      }
    jbyte *cData = env->GetByteArrayElements(data, 0);
    unsigned char *bdata = (unsigned char *)cData;
    seeta::ImageData imageData(bdata, width, height, channels);
      if(printLog) {
          LOGI("Native line 412 Info Detect imageData.width %d", imageData.width);
      }
      auto timeStart = getCurrentTime();
    auto faces = FD->detect(imageData);
      if(printLog) {
          LOGI("Native line 417 Info Detect times : %ld and faces.size : %d",
               getCurrentTime() - timeStart, faces.size);
      }

    if (faces.size > 0) {
        float similarity = 0;
        timeStart = getCurrentTime();
      std::vector<SeetaPointF> points(PD->number());
      PD->mark(imageData, faces.data[0].pos, points.data());
        if(printLog) {
            LOGI("Native line 427 Info Detect face PD->mark times : %ld", getCurrentTime() - timeStart);
        }

      jclass clazz_Face1Native = env->GetObjectClass(face1);
        if(printLog) {
            LOGI("Native line 432 Info Detect clazz_Face1Native %p", clazz_Face1Native);
        }
      jclass clazz_Face2Native = env->GetObjectClass(face2);
        if(printLog) {
            LOGI("Native line 436 Info Detect clazz_Face2Native %p", clazz_Face2Native);
        }
      jfieldID intFace1XID = env->GetFieldID(clazz_Face1Native, "x", "I");
      jfieldID intFace1YID = env->GetFieldID(clazz_Face1Native, "y", "I");
      jfieldID intFace1WidthID =
          env->GetFieldID(clazz_Face1Native, "width", "I");
      jfieldID intFace1HeightID =
          env->GetFieldID(clazz_Face1Native, "height", "I");
      jfieldID intFace2XID = env->GetFieldID(clazz_Face2Native, "x", "I");
      jfieldID intFace2YID = env->GetFieldID(clazz_Face2Native, "y", "I");
      jfieldID intFace2WidthID =
          env->GetFieldID(clazz_Face2Native, "width", "I");
      jfieldID intFace2HeightID =
          env->GetFieldID(clazz_Face2Native, "height", "I");

      jclass clazz_seetaPointsFaceNative = env->GetObjectClass(seetaPointsFace);
      jfieldID doubleleftEye_XID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "leftEye_X", "I");
      jfieldID doubleleftEye_YID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "leftEye_Y", "I");
      jfieldID doublerightEye_XID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "rightEye_X", "I");
      jfieldID doublerightEye_YID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "rightEye_Y", "I");
      jfieldID doublenose_XID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "nose_X", "I");
      jfieldID doublenose_YID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "nose_Y", "I");
      jfieldID doubleleftMouth_XID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "leftMouth_X", "I");
      jfieldID doubleleftMouth_YID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "leftMouth_Y", "I");
      jfieldID doublerightMouth_XID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "rightMouth_X", "I");
      jfieldID doublerightMouth_YID =
          env->GetFieldID(clazz_seetaPointsFaceNative, "rightMouth_Y", "I");

      //为了保证性能，每次最多返回两张脸的位置信息
      if (faces.size == 1) {
        // LOGI("为了保证性能，每次最多返回1张脸的位置信息");
        env->SetIntField(face1, intFace1XID, faces.data[0].pos.x);
        env->SetIntField(face1, intFace1YID, faces.data[0].pos.y);
        env->SetIntField(face1, intFace1WidthID, faces.data[0].pos.width);
        env->SetIntField(face1, intFace1HeightID, faces.data[0].pos.height);
      } else if (faces.size > 1) {
        // LOGI("为了保证性能，每次最多返回2张脸的位置信息");
        env->SetIntField(face1, intFace1XID, faces.data[0].pos.x);
        env->SetIntField(face1, intFace1YID, faces.data[0].pos.y);
        env->SetIntField(face1, intFace1WidthID, faces.data[0].pos.width);
        env->SetIntField(face1, intFace1HeightID, faces.data[0].pos.height);
        env->SetIntField(face2, intFace2XID, faces.data[1].pos.x);
        env->SetIntField(face2, intFace2YID, faces.data[1].pos.y);
        env->SetIntField(face2, intFace2WidthID, faces.data[1].pos.width);
        env->SetIntField(face2, intFace2HeightID, faces.data[1].pos.height);
      }

      if (isItProvidePoints == 1) {
        env->SetIntField(seetaPointsFace, doubleleftEye_XID, (jint)points[0].x);
        env->SetIntField(seetaPointsFace, doubleleftEye_YID, (jint)points[0].y);
        env->SetIntField(seetaPointsFace, doublerightEye_XID,
                         (jint)points[1].x);
        env->SetIntField(seetaPointsFace, doublerightEye_YID,
                         (jint)points[1].y);
        env->SetIntField(seetaPointsFace, doublenose_XID, (jint)points[2].x);
        env->SetIntField(seetaPointsFace, doublenose_YID, (jint)points[2].y);
        env->SetIntField(seetaPointsFace, doubleleftMouth_XID,
                         (jint)points[3].x);
        env->SetIntField(seetaPointsFace, doubleleftMouth_YID,
                         (jint)points[3].y);
        env->SetIntField(seetaPointsFace, doublerightMouth_XID,
                         (jint)points[4].x);
        env->SetIntField(seetaPointsFace, doublerightMouth_YID,
                         (jint)points[4].y);
          if(printLog) {
              for (SeetaPointF &seetaPointF : points) {
                  LOGI("Native line 511 Info Detector face points: %f %f", seetaPointF.x, seetaPointF.y);
              }
          }
      }

      env->DeleteLocalRef(clazz_Face1Native);
      env->DeleteLocalRef(clazz_Face2Native);
      env->DeleteLocalRef(clazz_seetaPointsFaceNative);

      return 0;
    }
  } else {
      if(printLog) {
          LOGI("Native line 524 Error  : FD is NULL");
      }
  }

  env->DeleteLocalRef(data);
  env->DeleteLocalRef(face1);
  env->DeleteLocalRef(face2);
  env->DeleteLocalRef(seetaPointsFace);
  return 0;
}
